Skip to content

Server Statistik Skript als Debian Paket erstellen/installieren

Mit dem Skript serverstats.sh haben wir uns ein Skript geschrieben, was als SystemV Service läuft und Statistiken des Host in einem Logfile schreibt. Das Skript verwendet /etc/default/serverstats als Konfiguration. Dabei werden vom Skript definierte Default Werte überschrieben. Außerdem ist für das Logfile eine Logfile Rotation unter /etc/logrotate.d/serverstats eingerichtet. Das Skript selbst ist unter /usr/local/share/serverstats/serverstats.sh installiert.

Aus diesen Skript und Konfigurationen werden wir uns nun ein Debian Paket erstellen, und es als root installieren.

Struktur erstellen

Die Debian Pakete werden als Benutzer gebaut, nur das installieren wird mir root ausgeführt. Wir erstellen uns als Benutzer in unseren Homeverzeichnis das Verzeichnis ~/build. Darin erstellen wird ein Verzeichnis, wie später das Debian Package heißen soll. In unserem Fall ist das serverstats.

tux@earth:~$ mkdir -p ~/build/serverstats && cd ~/build/serverstats

Dann gehen wir in das erstellte Verzeichnis und erstellen uns die Verzeichnis-Struktur, wie sie später im Dateisystem vorhanden sein soll.

tux@earth:~/build/serverstats$ mkdir -p DEBIAN
tux@earth:~/build/serverstats$ mkdir -p etc/{default,logrotate.d}
tux@earth:~/build/serverstats$ mkdir -p lib/systemd/system
tux@earth:~/build/serverstats$ mkdir -p usr/local/share/serverstats

Steuerungsdatei erstellen

Nun erstellen wir uns eine Steuerungsdatei (control) im DEBIAN Verzeichnis. In dieser Datei werden wir die wichtigsten Informationen zu unserem Paket angeben.

tux@earth:~/build/serverstats$ vi DEBIAN/control
Package: serverstats
Version: 0.0.1-0
Section: misc
Priority: optional
Architecture: all
Depends: awk,perl,grep,iproute2,bash,hostname,sed
Installed-Size: 9.7
Maintainer: Marko Schulz <info@tuxnet24.de>
Homepage: https://deb.tuxnet24.de/
Description: A shell script that runs as a SystemV
 service and collects statistics from the current host
 and stores them in a defined log file.
 .
 The default configuration can be overwritten in the file
 /etc/default/serverstats.

Wie wir sehen besteht die Datei aus einer Auflistung von Feldnamen gefolgt von einem Wert oder Text dafür. In der folgenden Tabelle schauen wir uns einmal die verwendeten Felder etwas genauer an:

Feldname Beschreibung
Package Der Name den das Paket haben soll. Er sollte möglichst noch nicht in Verwendung sein, da man über ihn auch das Paket im Paketmanager installieren kann. Weiters darf er auch keine Whitespaces (Leerzeichen etc.) oder Sonderzeichen enthalten.
Version Die Version des Pakets. Über die Version kann der Paketmanager auch erkennen ob eine eventuell schon installierte Version des Programmes neuer ist als die im Paket.
Section Gibt die Sektion an, zu der das Paket gehört. Zuerst geben wir die Kategorie 'main', 'contrib' oder 'non-free' gefolgt von einem Slash ('/') an. Wenn das Paket in die Kategorie 'main', dh. mit einer Open Source Lizenz lizenziert ist, dann können wir die Kategorie auch weglassen, da dann automatisch 'main' angenommen wird.
Anschließen müssen wir noch die Sektion angeben, bei der es sehr viele Möglichkeiten gibt. Einige Beispiele sind 'admin', 'devel' (development), 'doc', 'editors', 'games', 'graphics', 'math', 'misc', 'science' oder 'utils'. Es gibt noch ein paar weitere, die wir auf der Debian Homepage finden.
Priority Hier geben wir an wie wichtig unser Paket für ein System bzw. seinen Anwender ist. Normalerweise werden wir hier 'extra' angeben.
Architecture Gibt die Prozessorarchitektur an, auf denen dieses Programm/Skript lauffähig ist. Mögliche Werte sind 'i386' für die meisten Heimcomputer die mit 32 Bit laufen, 'amd64' für 64-Bit Heimcomputer und 'all' für alle Computer (zb. für viele Skripte). Es gibt zwar auch noch andere Möglichkeiten, die aber nicht so relevant sind.
Depends Abhängigkeiten, dh. Programme bzw. eigentlich Pakete die unser Programm/Skript zur Ausführung unbedingt braucht können wir hier angeben. Wir brauchen nur die Paketnamen getrennt durch Komma und Leerzeichen angeben, also zum Beispiel:
Depends: cmake, libsdl1.2, libsdl-image1.2
Installed-Size Wie der Name schon sagt geben wir hier die Größe unseres installierten Paketes an. Die Angabe erfolgt in KB.
Maintainer Hier geben wir den bzw. die Namen der Programmierer/Ersteller des Pakets/Programms an. Meistens wird hier auch eine E-Mail-Adresse angegeben.
Homepage Dieser Punkt sollte wohl selbsterklärend sein. Am Besten sollte man unter dieser Adresse auch den Quellcode des Pakets erhalten.
Description In der gleichen Zeile wie "Description" eine kurze Beschreibung des Pakets und anschließend in der nächsten Zeile eingerückt die ausführliche Beschreibung.

Benötigten Dateien/Skripte und Konfigurationen kopieren

Als erstes erstellen wir die Konfiguration für Logrotate.

tux@earth:~/build/serverstats$ vi etc/logrotat.d/serverstats
/var/log/serverstats.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 640 root adm
}

Dann kommt die Konfigurationsdatei.

tux@earth:~/build/serverstats$ vi etc/default/serverstats
#
# This is the configuration file for the Serverstats service
# (/lib/systemd/system/serverstats.service)
#

# Interval in seconds in which the statistics are written/retrieved.
# You can also use 1m => 60 seconds or 1h => 3600 seconds or 1d => 86400 seconds.
INTERVAL="5m"

# Path to the output logfile of the statistics.
# Important!!! If you change the path here, you have to change
# the path also in /etc/logrotate.d/serverstats.
OUTPUTFILE="/var/log/serverstats.log"

# Here you can define the logformat. Each %<name> is also a stat_<name> function.
# You can add parameters to some functions, like %<function>_<arg1>_<arg2>.
LOGFORMAT="%timestamp %hostname %loadavg %memoryusage %memorycached %swapusage %membuffer %cpuusage %diskspace %traffic"

# Unit for the interface traffic. You can define mb (megabytes) or kb (kilobytes).
# Default is bytes per seconds bytes/s.
UNIT="kb"

# vim: syntax=sh ts=4 sw=4 sts=4 sr noet

Es folgt die Service Unit Datei.

tux@earth:~/build/serverstats$ vi lib/systemd/system/serverstats.service
[Unit]
Description=collect memory/cpu statistics of the current server maschine

[Service]
Type=oneshot
ExecStart=/bin/sh -c "/usr/local/share/serverstats/serverstats.sh &"
ExecStop=/bin/sh -c "kill $(ps -fade | grep serverstats.sh | grep -v grep | awk '{print $2}')"
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Als letztes erstellen wir noch unser Skript.

tux@earth:~/build/serverstats$ vi usr/local/share/serverstats/serverstats.sh
#!/bin/bash
# **********************************************************************
# $File: serverstats.sh $
# $Author: mschulz $ - $Date: 2023-03-09 12:26:09 +0100 (Mi, 09 März 2023) $
# $Description: Script to collect memory/cpu statistics of the current server maschine. $
# **********************************************************************

# Define the idle time for this service. You can define 1d => 24h or 86400 seconds.
interval="1m"

# The path to the output logfile, where the statistics metrics will be storend
outfile="/var/log/serverstats.log"

# The default logformat. Each %name is a stat_<name> function.
# You can add parameters to some functions, like %<function>_<arg1>_<arg2>.
logformat="%timestamp %hostname %loadavg %memoryusage %memorycached %swapusage %membuffer %cpuusage %diskspace %traffic"

# Unit for the interface traffic. You can define mb or kb. default is bytes.
inet_unit="kb"

# Load configuration, when exists
if [ -f /etc/default/serverstats ]; then
        . /etc/default/serverstats

        [ -n "${INTERVAL}" ] && interval=${INTERVAL}
        [ -n "${OUTPUTFILE}" ] && outfile=${OUTPUTFILE}
        [ -n "${LOGFORMAT}" ] && logformat=${LOGFORMAT}
        [ -n "${UNIT}" ] && inet_unit=${UNIT}
fi

# Get the default interface with ip
inet_iface=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)' | head -n1)

# **********************************************************************
# This function converts time designations such as
# 1d or 12h into seconds. Supported are Xd, Xh, Xm, Xs.
#
# @param $1 - The time designations
# @return init
#
str2seconds () {

local item=$1
local digit=$(echo $item | perl -n -e 'print $1 if ($_ =~ m/^(\d+)((?:d|h|m|s))$/g);')
local unit=$(echo $item | perl -n -e 'print $2 if ($_ =~ m/^(\d+)((?:d|h|m|s))$/g);')
local sec

case $unit in
        d) sec=$((${digit}*86400)) ;;
        h) sec=$((${digit}*3600))  ;;
        m) sec=$((${digit}*60))    ;;
        s) sec=${digit}            ;;
esac

echo $sec

}

# **********************************************************************
# This function print out the timestamp for the record.
#
# @return string
#
stat_timestamp () {

# Print out the date in format like [09/Mar/2023:00:56:04 +0100]
echo -n "[$(date +%d/%m/%Y\ %H:%M:%S\ %z)] "

}

# **********************************************************************
# This function print out the hostname for the record.
#
# @return string
#
stat_hostname () {

# Print out the hostname
echo -n "\"$(hostname)\" "

}

# **********************************************************************
# This function get the network traffic statistics for an defined interface.
#
# @param $1 - The interface name
# @param $2 - The unit of the traffic (mb,kb,bytes)
# @param $3 - Verbose on|off
# @return string
#
stat_traffic () {

local iface=$1
local unit=${2:-"b"}
local verbose=${3:-"on"}
local label="Bytes/s"

local prefix="traffic,"

# Get received and transmited bytes of the current interface
local rxold=$(cat /sys/class/net/${iface}/statistics/rx_bytes 2>/dev/null)
local txold=$(cat /sys/class/net/${iface}/statistics/tx_bytes 2>/dev/null)

# We wait one second
sleep 1

# Get received and transmited bytes of the current interface
local rxnew=$(cat /sys/class/net/${iface}/statistics/rx_bytes 2>/dev/null)
local txnew=$(cat /sys/class/net/${iface}/statistics/tx_bytes 2>/dev/null)

# Calculate received and transmited Byte/KB/MB/ per second of the current interface
if [ $unit = "KB" -o $unit = "kb" ]; then
        tx=$(echo "$txnew $txold" | perl -a -n -e 'printf("%.2f", (($F[0]-$F[1])/1024));')
        rx=$(echo "$rxnew $rxold" | perl -a -n -e 'printf("%.2f", (($F[0]-$F[1])/1024));')
        label="KB/s"
elif [ $unit = "MB" -o $unit = "mb" ]; then
        tx=$(echo "$txnew $txold" | perl -a -n -e 'printf("%.2f", (($F[0]-$F[1])/131072));')
        rx=$(echo "$rxnew $rxold" | perl -a -n -e 'printf("%.2f", (($F[0]-$F[1])/131072));')
        label="MB/s"
else
        tx=$(echo "$txnew $txold" | perl -a -n -e 'printf("%.2f", ($F[0]-$F[1]));')
        rx=$(echo "$rxnew $rxold" | perl -a -n -e 'printf("%.2f", ($F[0]-$F[1]));')
fi

if [ $verbose = "on" ]; then
        local suffix="RX $iface: $rx ${label}, TX $iface: $tx ${label}"
else
        local suffix="net_rx=${rx},net_tx=${tx}"
fi

echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the Load Average (1,5,15) statistics.
#
# @return string
#
stat_loadavg () {

local prefix="loadavg,"
local suffix=$(awk '{print "loadavg1="$1",loadavg5="$2",loadavg15="$3}' /proc/loadavg)
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the Physical Memory Usage (total, free) statistics.
#
# @return string
#
stat_memoryusage () {

local memory=$(awk '$0 ~ /^(MemFree|MemTotal)/{ printf "%%s=%s,", $2 }' /proc/meminfo | sed 's/,$//g')
local prefix="memory,"
local suffix=$(printf "${memory}" "mem_total" "mem_free")
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the Cached Memory Usage (free, buffers, cache) statistics.
#
# @return string
#
stat_memorycached () {

local memcache=$(awk '$0 ~ /^(MemFree|Buffers|Cached)/{ printf "%%s=%s,", $2 }' /proc/meminfo | sed 's/,$//g')
local prefix="memcache,"
local suffix=$(printf "${memcache}" "mem_free" "mem_buffers" "mem_cache")
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the Swap Usage (total, free) statistics.
#
# @return string
#
stat_swapusage () {

local swapusage=$(awk '$0 ~ /^(SwapFree|SwapTotal)/{ printf "%%s=%s,", $2 }' /proc/meminfo | sed 's/,$//g')
local prefix="swapusage,"
local suffix=$(printf "${swapusage}" "swap_total" "swap_free")
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the Memory Buffers (total, buffers) statistics.
#
# @return string
#
stat_membuffer () {

local membuffer=$(awk '$0 ~ /^(Buffers|MemTotal)/{ printf "%%s=%s,", $2 }' /proc/meminfo | sed 's/,$//g')
local prefix="membuffer,"
local suffix=$(printf "${membuffer}" "mem_total" "mem_buffers")
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the Used Space / (total, used) statistics.
#
# @param $1 - The mountpoint, default is /
# @return string
#
stat_diskspace () {

local mount=${1:-"/"}
local label=${mount}

# Mask shlashes
mount=$( echo ${mount} | sed 's!\/!\\\/!g' )

local suffix=$(df | awk '$6 ~ /^'${mount}'$/ {print "space_total="$2",space_used="$3}')
local prefix="usedspace_${label},"
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# This function get the CPU Usage (user,nice,system,idle,iowait,irq) statistics.
#
# @return string
#
stat_cpuusage () {

local prefix="cpuusage,"
local cpu=$(top -n1 -b | grep '%Cpu(s)' | sed 's/\,/\./g')
local system=$(echo $cpu | awk '{printf "%.2f", $4}')
local user=$(echo $cpu | awk '{printf "%.2f", $2}')
local nice=$(echo $cpu | awk '{printf "%.2f", $6}')
local total=$(awk '{u=$2+$4; t=$2+$4+$5; if (NR==1){u1=u; t1=t;} else printf "%.2f\n", ($2+$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat))
local suffix="system=${system},user=${user},nice=${nice},total=${total}"
echo -n "\"${prefix}${suffix}\" "

}

# **********************************************************************
# MAIN

# Get the interval as seconds
interval=$(str2seconds "${interval}")

# Check, if any interface was defined for traffic statistics (1=yes|0=no)
default=$( echo ${logformat} | perl -ne 'print ($_ =~ m/\%traffic_\w+/g && "1" || "0")' )

# Set default parameter for network traffic
if [ ${default} -eq 0 ]; then
        logformat=$( echo ${logformat} | perl -p -n -e 's#\%traffic#\%traffic_'${inet_iface}'_'${inet_unit}'#g')
fi

# Start endless loop
while true; do

        # Collect metrics and write it to output file
        (
        for item in ${logformat}; do
                eval "$(echo ${item} | perl -F_ -a -n -e '$func = shift @F; $func =~ s/\%//g; print "stat_",$func, " @F";')"
        done
        echo
        ) >>${outfile}

        # Sleep for X seconds
        sleep ${interval}

done

# vim: syntax=sh ts=4 sw=4 sts=4 sr noet
# EOF

Ich erstelle mit folgendem Befehl noch eine md5sum Datei, in der alle Dateien als md5 Hash festgehalten sind.

tux@earth:~/build/serverstats$ find {etc,lib,usr}/ -type f -exec md5sum '{}' \; >md5sums

Bevor wir nun zum bauen kommen, setzten wir noch die Berechtigungen, wie diese später im System vorhanden sein sollen.

tux@earth:~/build/serverstats$ find ./ -type d -exec chmod 755 '{}' \;
tux@earth:~/build/serverstats$ find ./ -type f -exec chmod 644 '{}' \;
tux@earth:~/build/serverstats$ find ./ -name '*.sh' -exec chmod 755 '{}' \;
tux@earth:~/build/serverstats$ sudo chown -R root.root *

Ein Debian Paket bauen

Das bauen des Debian Paket ist eigentlich der geringste Aufwand. Dazu geben wir einfach folgenden Befehl ein. Zuvor gehen wir im Verzeichnis noch eine Ebene höher in das ~/build Verzeichnis.

tux@earth:~/build/serverstats$ cd ../
tux@earth:~/build$ dpkg -b ./serverstats serverstats.deb
dpkg-deb: Paket »serverstats« wird in »serverstats.deb« gebaut.

Informationen des Paketes anzeigen lassen

Mit folgendem Befehl können wir uns Informationen zum neu erstellen Paket anzeigen lassen.

tux@earth:~/build$ dpkg -I serverstats.deb
 neues Debian-Paket, Version 2.0.
 Größe 4192 Byte: control-Archiv= 556 Byte.
     506 Byte,    15 Zeilen      control
 Package: serverstats
 Version: 0.0.1-0
 Section: misc
 Priority: optional
 Architecture: all
 Depends: awk,perl,grep,iproute2,bash,hostname,sed
 Installed-Size: 9.7
 Maintainer: Marko Schulz <info@tuxnet24.de>
 Homepage: https://deb.tuxnet24.de/
 Description: A shell script that runs as a SystemV
  service and collects statistics from the current host
  and stores them in a defined log file.
  .
  The default configuration can be overwritten in the file
  /etc/default/serverstats.

Debian Paket installieren

Zum installieren verwenden wir für dpkg -i (install). Dabei werden die Abhängigkeiten geprüft und das Paket im System installiert.

tux@earth:~/build$ sudo dpkg -i serverstats.deb
(Lese Datenbank ... 77020 Dateien und Verzeichnisse sind derzeit installiert.)
Vorbereitung zum Entpacken von serverstats.deb ...
Entpacken von serverstats (0.0.1-0) über (0.0.1-0) ...
serverstats (0.0.1-0) wird eingerichtet ...

Das das Paketerfolgreich im System installiert wurde, können wir uns ebenfalls mit dpkg -l (list) anzeigen lassen.

tux@earth:~/build$ dpkg -l | grep serverstats
ii  serverstats                    0.0.1-0                        all          A shell script that runs as a SystemV

Serverstats aktivieren und starten

Im Grunde ist schon alles vorkonfiguriert und wir können nun das Skript als Service aktivieren und starten.

Den Service aktivieren wir wie folgt.

root:~# systemctl enable serverstats
Created symlink /etc/systemd/system/multi-user.target.wants/serverstats.service → /lib/systemd/system/serverstats.service.

Mit der Option start, starten wir den Service.

root:~# systemctl start serverstats

Den aktuellen Status können wir uns mit der Option status anzeigen lassen.

root:~# systemctl status serverstats
● serverstats.service - collect memory/cpu statistics of the current server maschine
     Loaded: loaded (/lib/systemd/system/serverstats.service; enabled; vendor preset: enabled)
     Active: active (exited) since Fri 2023-03-10 11:52:20 CET; 8s ago
    Process: 2918618 ExecStart=/bin/sh -c /usr/local/share/serverstats/serverstats.sh & (code=exited, status=0/SUCCESS)
   Main PID: 2918618 (code=exited, status=0/SUCCESS)
      Tasks: 2 (limit: 4074)
     Memory: 1.0M
        CPU: 136ms
     CGroup: /system.slice/serverstats.service
             ├─2918619 /bin/bash /usr/local/share/serverstats/serverstats.sh
             └─2918734 sleep 300

Mär 10 11:52:20 sudebrp2378 systemd[1]: Starting collect memory/cpu statistics of the current server maschine...
Mär 10 11:52:20 sudebrp2378 systemd[1]: Finished collect memory/cpu statistics of the current server maschine.

Wenn man sich in der Zwischenzeit eine Tasse Kaffee gönnt, wird man nach ca. 20 Minuten sehen, dass schon fleißig Statistiken geschrieben wurden.

root:~# cat /var/log/serverstats.log
[10/03/2023 11:52:20 +0100] "earth" "loadavg,loadavg1=0.16,loadavg5=0.12,loadavg15=0.04" "memory,mem_total=3510052,mem_free=946948" "memcache,mem_free=946948,mem_buffers=220320,mem_cache=1992608" "swapusage,swap_total=0,swap_free=0" "membuffer,mem_total=3510052,mem_buffers=220320" "cpuusage,system=0,00,user=0,00,nice=0,00,total=0,00" "usedspace_/,space_total=30786468,space_used=17202404" "traffic,RX eth0: 0.00 KB/s, TX eth0: 0.00 KB/s"
[10/03/2023 11:57:22 +0100] "earth" "loadavg,loadavg1=0.05,loadavg5=0.08,loadavg15=0.03" "memory,mem_total=3510052,mem_free=918464" "memcache,mem_free=918464,mem_buffers=221228,mem_cache=2019584" "swapusage,swap_total=0,swap_free=0" "membuffer,mem_total=3510052,mem_buffers=221228" "cpuusage,system=0,00,user=0,00,nice=0,00,total=0,99" "usedspace_/,space_total=30786468,space_used=17202652" "traffic,RX eth0: 460.44 KB/s, TX eth0: 20.59 KB/s"
[10/03/2023 12:02:25 +0100] "earth" "loadavg,loadavg1=0.03,loadavg5=0.11,loadavg15=0.07" "memory,mem_total=3510052,mem_free=922976" "memcache,mem_free=922976,mem_buffers=221292,mem_cache=2020364" "swapusage,swap_total=0,swap_free=0" "membuffer,mem_total=3510052,mem_buffers=221292" "cpuusage,system=0,00,user=0,00,nice=0,00,total=4,08" "usedspace_/,space_total=30786468,space_used=17202944" "traffic,RX eth0: 0.00 KB/s, TX eth0: 0.00 KB/s"
[10/03/2023 12:07:27 +0100] "earth" "loadavg,loadavg1=0.07,loadavg5=0.10,loadavg15=0.08" "memory,mem_total=3510052,mem_free=918188" "memcache,mem_free=918188,mem_buffers=221308,mem_cache=2020968" "swapusage,swap_total=0,swap_free=0" "membuffer,mem_total=3510052,mem_buffers=221308" "cpuusage,system=0,00,user=0,00,nice=0,00,total=8,00" "usedspace_/,space_total=30786468,space_used=17203080" "traffic,RX eth0: 0.18 KB/s, TX eth0: 0.09 KB/s"

Das Debian Paket wieder entfernen

Das Paket können wir mit apt-get und der Option remove wieder aus dem System entfernen.

root:~# sudo apt-get remove serverstats

Debian-Paket herunterladen

Hier könnt ihr das fertige Debian-Paket herunterladen.

Datei: serverstats.deb

Datum: März 12, 2023

Benutzer: Marko Schulz